package com.esdk.utils;

import com.esdk.esdk;
import com.esdk.interfaces.Callback3;

import java.io.FileInputStream;
import java.util.AbstractMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ffychina@163.com
 * 实现过期或超过数量限制时自动清理，提供自定义回调方法
 */

public class CacheMap<K,V>extends AbstractMap<K,V>{

	private static CacheMap<Object,Object> defaultInstance;

	/**移除时触发处理事件*/
	private Callback3 _removedEventHandler;

	public static synchronized final CacheMap<?,?> getDefault(){
		if(defaultInstance==null){
			defaultInstance=new CacheMap();
		}
		return defaultInstance;
	}

	private int _cacheTimeoutSec=60*15; //如果<=0则永不过期
	private int _limitSize=1000;
	private ConcurrentHashMap<K,CacheEntry> map=new ConcurrentHashMap<K,CacheEntry>();

	public CacheMap(){
	}

	public CacheMap(int timeoutSec){
		this._cacheTimeoutSec=timeoutSec;
	}

	public CacheMap(int timeoutSec,int limitSize){
		this(timeoutSec);
		this._limitSize=limitSize;
	}

	/**
	 * @param removedFn 移除时触发处理事件
	 * */
	public <T> CacheMap(Callback3<K,V,T> removedFn){
		this._removedEventHandler=removedFn;
	}

	/**
	 * @param limitSize 限制key数量
	 * @param timeoutSec 缓存超时秒数
	 * @param removedFn 移除时触发处理事件
	 * */
	public <T> CacheMap(int timeoutSec,int limitSize,Callback3<K,V,T> removedFn){
		this(timeoutSec);
		this._limitSize=limitSize<=0?Integer.MAX_VALUE:limitSize;
		this._removedEventHandler=removedFn;
	}

	/**
	 * @param removedFn 移除时触发处理事件
	 * */
	public <T> CacheMap setRemovedHandler(Callback3<K,V,T> removedFn){
		this._removedEventHandler=removedFn;
		return this;
	}

	@Override
	public Set<Entry<K,V>> entrySet(){
		Set<Entry<K,V>> entrySet=new HashSet();
		Set<Entry<K,CacheEntry>> wrapEntrySet=map.entrySet();
		for(Entry<K,CacheEntry> entry:wrapEntrySet){
			entrySet.add(entry.getValue());
		}
		return entrySet;
	}

	private long lastCleanCacheTime=System.currentTimeMillis();

	/**在get()时删除超时的key*/
	@Override public V get(Object key){
		V result=null;
		long now=System.currentTimeMillis();
		if(_cacheTimeoutSec>0 && now-lastCleanCacheTime>_cacheTimeoutSec*1000) {
			int expiredRemovedCount=0;
			V removed=null;
			for(Iterator iter=map.entrySet().iterator();iter.hasNext();) {
				Entry<K,CacheEntry> entry=(Entry)iter.next();
				CacheEntry cacheEntry=entry.getValue();
				if(now>cacheEntry.expiredMills) {
					removed=remove(entry.getKey());
					if(this._removedEventHandler!=null&&removed!=null){
						_removedEventHandler.invoke(entry.getKey(),removed,cacheEntry.expiredSec);
					}
					expiredRemovedCount++;
				}
			}
			lastCleanCacheTime=now;
			if(expiredRemovedCount>0)
				esdk.info("缓存超时{}秒自动清理，已清理{}个，当前size为{}，最新移除的实例为{}",_cacheTimeoutSec,expiredRemovedCount,map.size(),removed);
		}
		CacheEntry entry=map.get(key);
		if(entry==null)
			result=null;
		else if(now<entry.expiredMills){
			result=entry.value;
		}else{
			remove(key);
		}
		return result;
	}

	@Override
	public V put(K key,V value){
		return put(key,value,_cacheTimeoutSec);
	}

	/**在put()时删除超时缓存数量的key
	 * @param expiredSec 可以指定每个key的过期时间，<=0为不过期
	 * */
	public V put(K key,V value,int expiredSec){
		if(map.size()>_limitSize){
			Object[] keys=map.keySet().toArray();
			for(int i=0,n=keys.length/2;i<n;i++){
				V removed=remove(keys[i]);//如果数据量超过10万以上，这里可能会有性能问题
				if(this._removedEventHandler!=null&&removed!=null){
					_removedEventHandler.invoke((K)keys[i],removed,-2); //暂时用-2表示数据量超过limitsize
				}
			}
			esdk.warn("超过缓存数量{}上限自动清理，已清理{}个，当前size为{}",_limitSize,keys.length,map.size());
		}
		CacheEntry entry=new CacheEntry(key,value,expiredSec);
		map.put(key,entry);
		return value;
	}

	@Override
	public void clear(){
		map.clear();
	}

	@Override
	public V remove(Object key){
		CacheEntry entry=map.remove(key);
		V oldV=entry!=null?(V)entry.getValue():null;
		return oldV;
	}

	public int getExpireSec(){
		return _cacheTimeoutSec;
	}

	public int getLimitSize(){
		return _limitSize;
	}

	private class CacheEntry implements Entry<K,V>{
		long expiredMills;
		int expiredSec;
		V value;
		K key;
		CacheEntry(K key,V value){
			this(key,value,_cacheTimeoutSec);
		}
		CacheEntry(K key,V value,int expiredSec){
			super();
			this.value=value;
			this.key=key;
			this.expiredMills=expiredSec>0?System.currentTimeMillis()+expiredSec*1000:Long.MAX_VALUE;
			this.expiredSec=expiredSec;
		}

		@Override
		public K getKey(){
			return key;
		}

		@Override
		public V getValue(){
			return value;
		}

		@Override
		public V setValue(V value){
			return this.value=value;
		}
	}


	public static void main(String[] args) throws Exception{
		CacheMap cachemap=new CacheMap(1,5000);
		int n=6000;
		for(int i=0;i<n;i++){
			cachemap.put("key"+i,"value"+i);
		}
		esdk.tool.assertEquals(cachemap.get("key"+(n-1)),"value5999");
		esdk.tool.assertEquals(cachemap.get("key0"),null);
		esdk.tool.sleep(1000);
		esdk.tool.assertEquals(cachemap.get("key"+(n-1)),null);
		cachemap.put("a","a1",1);
		esdk.tool.assertEquals(cachemap.get("a"),"a1");
		esdk.tool.sleep(1000);
		esdk.tool.assertEquals(cachemap.get("a"),null);
		esdk.tool.assertEquals(cachemap.remove("a"),null);
		CacheMap<String,FileInputStream> cachemap1=new CacheMap<>(1,5000,(k,v,t)->{
      v.close();
    });
		FileInputStream fis=new FileInputStream("./testfiles/act_presence.xlsx");
		esdk.tool.assertEquals(fis.getFD().valid(),true);
		cachemap1.put("file1",fis);
		esdk.tool.sleep(1000);
		esdk.tool.assertEquals(cachemap1.get("file1"),null);
		esdk.tool.assertEquals(fis.getFD().valid(),false);
	}
}